/*! @brief Objects for 2D geometry (plane objects)
 *
 * TODO :: value of the line in this implementation differs from the
 * distance between line and point!!
 */

# pragma once

# include <cassert>
# include <vector>

# include <iostream>

# include "geometry_details.hpp"

namespace geometry { namespace d2 {

    /*! @brief Point presentation
     */
    class Point : public details::CoordinateObject<double> {
    public:

        /*! @brief Point constuctor
         */
        Point(double x, double y) noexcept
          : details::CoordinateObject<double>(x, y) {}

        /*! @brief Copy constructor
         */
        Point(Point const& p) noexcept
          : details::CoordinateObject<double>(p) {}

    };


    /*! @brief Vector presentation
     */
    class Vector : public details::CoordinateObject<double> {
    public:

        /*! @brief Vector constructor
         */
        Vector(double x, double y) noexcept
          : details::CoordinateObject<double>(x, y) {}

        /*! @brief Copy constructor
         */
        Vector(Vector const& v) noexcept
          : details::CoordinateObject<double>(v) {}
    };


    /*! @brief Plus operation on vectors
     */
    inline Vector operator+(Vector const& p, Vector const& q) noexcept {
        return Vector(p.x() + q.x(), p.y() + q.y());
    }


    /*! @brief Minus operation
     */
    inline Vector operator-(Vector const& p, Vector const& q) noexcept {
        return Vector(p.x() - q.x(), p.y() - q.y());
    }


    /*! @brief Multiplication
     */
    inline Vector operator*(Vector const& p, double k) noexcept {
        return Vector(k * p.x(), k * p.y());
    }


    /*! @brief Multiplication
     */
    inline Vector operator*(double k, Vector const& p) noexcept {
        return Vector(k * p.x(), k * p.y());
    }


    /*! @brief Dot operation (inner product)
     */
    inline double dot(Vector const& p, Vector const& q) noexcept {
        return p.x() * q.x() + p.y() * q.y();
    }


    /*! @brief Line on a plane
     */
    class Line {
        friend double dist(Line const&, Point const&);
    public:

        /*! @brief Line constructor
         *
         * @param x
         * @param y
         * @param z
         *
         * Creates line with 3 coefficients. First of them determine
         * the coefficients at x and y.
         */
        Line(double x, double y, double z) noexcept
            : cx_(x), cy_(y), c_(z) {}

        /*! @brief Line constructor
         */
        Line(Vector const& e, Point const& p) noexcept
            : cx_(e.y()), cy_(-e.x()), c_(e.x() * p.y() - e.y() * p.x()) {}


        /*! @brief Line constructor
         */
        Line(Point const& p, Point const& q) noexcept
            : cx_(q.y() - p.y()), cy_(p.x() - q.x()),
              c_((q.x() - p.x()) * p.y() - (q.y() - p.y()) * p.x()) {}

        /*! @brief Value on the point
         */
        inline double operator()(Point const& p) const noexcept {
          return cx_ * p.x() + cy_ * p.y() + c_;
        }

    private:

        double cx_;
        double cy_;
        double c_;

    };


    /*! @brief Computes distance between line and points
     */
    double dist(Line const& L, Point const& p);


    /*! @brief Simplify the curve
     */
    std::vector<Point> simplifyCurve(std::vector<Point> const& curve, double const eps);

}}
